﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using ParserGenerator.CharacterMapping;

namespace ParserGenerator.ScannerConstruction
{
    [Serializable]
    public sealed class ScannerAutomatonStateTable : ReadOnlyCollection<ScannerAutomatonState>
    {
        public ScannerAutomaton ScannerAutomaton { get; }
        public ScannerAutomatonReductionsTable Reductions { get; }
        public ScannerAutomatonStatePredecessorsTable Predecessors { get; }
        public CharMap CharMap => ScannerAutomaton.CharMap;
        public ScannerAutomatonStartTable Starts => ScannerAutomaton.Starts;

        private readonly Dictionary<ScannerAutomatonItemSet, ScannerAutomatonState> _dict;
        private readonly Queue<ScannerAutomatonState> _queue;

        internal ScannerAutomatonStateTable(ScannerAutomaton scannerAutomaton)
            : base(new List<ScannerAutomatonState>())
        {
            if (scannerAutomaton == null)
                throw new ArgumentNullException(nameof(scannerAutomaton));
            ScannerAutomaton = scannerAutomaton;
            _dict = new Dictionary<ScannerAutomatonItemSet, ScannerAutomatonState>();
            _queue = new Queue<ScannerAutomatonState>();
            ConstructStates();
            _dict = null;
            _queue = null;
            Minimize();
            Optimize();
            Reductions = new ScannerAutomatonReductionsTable(this);
            Predecessors = new ScannerAutomatonStatePredecessorsTable(this);
        }

        private ScannerAutomatonState GetStateOrCreate(ScannerAutomatonItemSet items)
        {
            ScannerAutomatonState state;
            if (!_dict.TryGetValue(items, out state))
            {
                state = new ScannerAutomatonState(this, items);
                Items.Add(state);
                _dict.Add(items, state);
                _queue.Enqueue(state);
            }
            return state;
        }

        private void ConstructStates()
        {
            foreach (ScannerAutomatonStart start in Starts.Values)
            {
                start.State = GetStateOrCreate(start.Items);
                start.StrictState = GetStateOrCreate(start.StrictItems);
            }
            while (_queue.Count > 0)
            {
                ScannerAutomatonState source = _queue.Dequeue();
                foreach (var t in source.Partition())
                {
                    ScannerAutomatonState target = GetStateOrCreate(t.Value);
                    source.AddTransition(t.Key, target);
                }
            }
        }

        private void Minimize()
        {
            if (Items.Count <= 0)
                return;
            var groups = new List<ScannerAutomatonStateGroup>();
            var initGroup = new ScannerAutomatonStateGroup(this);
            groups.Add(initGroup);
            while (true)
            {
                int count = groups.Count;
                for (int i = 0; i < count; i++)
                    groups[i].Split(groups);
                if (count == groups.Count)
                    break;
            }
            groups.Sort((x, y) => x.Number.CompareTo(y.Number));
            Items.Clear();
            foreach (ScannerAutomatonStateGroup group in groups)
            {
                ScannerAutomatonState state = group.Representative;
                // Copy transition keys so that transition dictionary can be updated.
                CharClass[] symbols = state.Transitions.Keys.ToArray();
                foreach (CharClass symbol in symbols)
                {
                    ScannerAutomatonTransition tran = state.GetTransition(symbol);
                    ScannerAutomatonState target = tran.Target.Group.Representative;
                    state.UpdateTransition(symbol, target);
                }
                state.Core = null;
                Items.Add(state);
            }
            foreach (ScannerAutomatonStart start in Starts.Values)
            {
                start.State = start.State.Group.Representative;
                start.StrictState = start.StrictState.Group.Representative;
                start.Items = null;
                start.StrictItems = null;
            }
            for (int i = 0; i < Items.Count; i++)
            {
                ScannerAutomatonState state = Items[i];
                state.Number = i;
                state.Group = null;
            }
        }

        private void Optimize()
        {
            var dicts = new Dictionary<ScannerAutomatonState, CharClassBuilder>[Items.Count];
            foreach (ScannerAutomatonState state in Items)
            {
                var dict = new Dictionary<ScannerAutomatonState, CharClassBuilder>();
                foreach (var t in state.Transitions)
                {
                    CharClassBuilder ccb;
                    if (!dict.TryGetValue(t.Value.Target, out ccb))
                    {
                        ccb = new CharClassBuilder(CharMap.Universal);
                        dict.Add(t.Value.Target, ccb);
                    }
                    ccb.AddRange(t.Key.Intervals);
                }
                dicts[state.Number] = dict;
                state.Transitions.Clear();
            }
            var dicts2 = new Dictionary<ScannerAutomatonState, CharClass>[Items.Count];
            CharMapBuilder charMapBuilder = new CharMapBuilder(CharMap.Universal);
            for (int i = 0; i < Items.Count; i++)
            {
                var dict2 = new Dictionary<ScannerAutomatonState, CharClass>();
                Dictionary<ScannerAutomatonState, CharClassBuilder> dict = dicts[i];
                foreach (var t in dict)
                {
                    CharClass cc = t.Value.Build();
                    CharClass value = charMapBuilder.Intern(cc);
                    dict2.Add(t.Key, value);
                }
                dicts2[i] = dict2;
            }
            dicts = null;
            CharMap charMap = charMapBuilder.Build();
            foreach (ScannerAutomatonState state in Items)
            {
                Dictionary<ScannerAutomatonState, CharClass> dict2 = dicts2[state.Number];
                var dict = new SortedDictionary<CharClass, ScannerAutomatonState>();
                foreach (var t in dict2)
                {
                    ReadOnlyCollection<int> ecs = charMap.Map[t.Value];
                    foreach (int num in ecs)
                    {
                        CharClass ec = charMap.Classes[num];
                        dict.Add(ec, t.Key);
                    }
                }
                foreach (var t in dict)
                    state.AddTransition(t.Key, t.Value);
            }
            ScannerAutomaton.CharMap = charMap;
        }
    }
}
